@hyperframes/studio 0.8.144 → 0.8.146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-Bw_jIaSX.js → hyperframes-player-tkxyWf5H.js} +3 -3
- package/dist/assets/{index-Cj7W_c7p.js → index-BrdqOkqP.js} +1 -1
- package/dist/assets/{index-BVM9NuWl.js → index-Cg48BxAo.js} +145 -145
- package/dist/assets/{index-DyH9dT7b.js → index-Dllxg4aU.js} +1 -1
- package/dist/{chunk-EEXSYY5F.js → chunk-C5BDT7NG.js} +0 -1
- package/dist/{chunk-PEAKCOUW.js → chunk-HZDCMUG4.js} +2 -100
- package/dist/{chunk-PZ5AY32C.js → chunk-MLKGABMK.js} +0 -1
- package/dist/{chunk-G3EDKRZH.js → chunk-PHGBG7OG.js} +100 -5
- package/dist/{chunk-Y2OHHNM2.js → chunk-PVFBY7KR.js} +0 -1
- package/dist/{chunk-3GHTHPZD.js → chunk-T43DM7Q6.js} +101 -3
- package/dist/{domEditingLayers-PYST4G45.js → domEditingLayers-SMVA3FEW.js} +3 -4
- package/dist/index.css +0 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.html +1 -1
- package/dist/index.js +509 -529
- package/dist/player.js +4 -5
- package/dist/styles/tailwind-preset.js +1 -2
- package/dist/ui.js +3 -4
- package/package.json +8 -8
- package/src/captions/components/CaptionOverlay.tsx +1 -1
- package/src/captions/hooks/useCaptionSync.ts +1 -1
- package/src/components/LinkedSelectionToggle.tsx +2 -2
- package/src/components/LintModal.tsx +1 -5
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/ProjectUnreachableBanner.tsx +1 -1
- package/src/components/StudioLeftPanels.tsx +2 -2
- package/src/components/StudioRightPanels.tsx +1 -1
- package/src/components/TimelineToolbar.test.tsx +2 -2
- package/src/components/TimelineToolbar.tsx +6 -6
- package/src/components/editor/AnimationCard.tsx +3 -3
- package/src/components/editor/AnimationCardParts.tsx +1 -1
- package/src/components/editor/BlockParamsPanel.tsx +3 -3
- package/src/components/editor/ComputedTweenNotice.tsx +2 -2
- package/src/components/editor/FileTree.tsx +1 -1
- package/src/components/editor/GsapAnimationList.tsx +1 -1
- package/src/components/editor/GsapAnimationSection.tsx +2 -2
- package/src/components/editor/LayersPanel.test.ts +4 -4
- package/src/components/editor/LayersPanel.tsx +2 -2
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PromotableControl.tsx +1 -1
- package/src/components/editor/PropertyPanel.tsx +2 -2
- package/src/components/editor/SnapToolbar.tsx +2 -2
- package/src/components/editor/TopologyLens.test.tsx +5 -4
- package/src/components/editor/Transform3DCube.tsx +1 -1
- package/src/components/editor/gsapAnimationConstants.ts +2 -2
- package/src/components/editor/gsapAnimationHelpers.test.ts +1 -1
- package/src/components/editor/gsapAnimationHelpers.ts +1 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +1 -1
- package/src/components/editor/propertyPanelFill.tsx +1 -1
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +3 -3
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +2 -2
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +1 -1
- package/src/components/editor/propertyPanelFlatTextSection.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +3 -3
- package/src/components/editor/propertyPanelFxBandRuler.tsx +2 -2
- package/src/components/editor/propertyPanelFxNodeOpenBody.tsx +2 -2
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTransformCommit.ts +3 -3
- package/src/components/editor/useColorGradingController.ts +1 -1
- package/src/components/panels/SlideshowSubPanels.tsx +2 -2
- package/src/components/panels/VariablesBindElement.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +3 -3
- package/src/components/renders/GpuEncodingToggle.tsx +28 -0
- package/src/components/renders/RenderQueue.test.tsx +20 -2
- package/src/components/renders/RenderQueue.tsx +15 -3
- package/src/components/renders/RenderQueuePanel.tsx +2 -1
- package/src/components/renders/renderSettings.ts +5 -2
- package/src/components/renders/useRenderQueue.ts +6 -3
- package/src/components/renders/useRenderQueueComposition.test.tsx +12 -0
- package/src/components/sidebar/AssetCard.tsx +2 -2
- package/src/components/sidebar/AudioRow.tsx +1 -1
- package/src/components/sidebar/BlocksTab.tsx +2 -2
- package/src/components/sidebar/CompositionsTab.tsx +2 -2
- package/src/components/sidebar/PromptPreviewModal.tsx +1 -1
- package/src/hooks/timelineAudioGroupCreate.ts +1 -1
- package/src/hooks/timelineEditingHelpers.ts +1 -1
- package/src/hooks/useBlockHandlers.ts +1 -1
- package/src/hooks/useConsoleErrorCapture.test.tsx +21 -2
- package/src/hooks/useConsoleErrorCapture.ts +3 -2
- package/src/hooks/useDomEditSession.ts +1 -1
- package/src/hooks/useEditorSave.ts +1 -1
- package/src/hooks/useFrameCapture.ts +1 -1
- package/src/hooks/useGestureCommit.ts +4 -4
- package/src/hooks/useGsapScriptCommits.ts +1 -1
- package/src/index.ts +1 -0
- package/src/player/components/AudioGainDialog.test.tsx +1 -1
- package/src/player/components/AudioGainDialog.tsx +2 -1
- package/src/player/components/EditModal.tsx +2 -2
- package/src/player/components/KeyframeDiamondContextMenu.tsx +1 -1
- package/src/player/components/OutOfSyncBadge.tsx +1 -1
- package/src/player/components/Player.test.ts +44 -0
- package/src/player/components/Player.tsx +12 -1
- package/src/player/components/ShortcutsPanel.tsx +2 -2
- package/src/player/components/Timeline.virtualization.test.tsx +1 -1
- package/src/player/components/TimelineAutomationLaneSlot.tsx +1 -1
- package/src/player/components/TimelineClip.tsx +1 -1
- package/src/player/components/TimelineFxButton.tsx +2 -2
- package/src/player/components/TimelineTrackHeader.test.tsx +3 -3
- package/src/player/components/timelineClipDragCommit.ts +2 -2
- package/src/player/components/trackHeaderLabelRows.tsx +1 -1
- package/src/player/lib/playbackAdapter.ts +1 -1
- package/src/player/lib/playbackTypes.ts +1 -0
- package/src/player/sceneSwap.test.ts +27 -1
- package/src/player/sceneSwap.ts +16 -2
- package/src/utils/hmrStableContext.ts +1 -1
- package/src/utils/razorSplitTransaction.ts +0 -2
- package/src/utils/sdkCutover.linkedTiming.test.ts +1 -1
- package/dist/chunk-3GHTHPZD.js.map +0 -1
- package/dist/chunk-EEXSYY5F.js.map +0 -1
- package/dist/chunk-G3EDKRZH.js.map +0 -1
- package/dist/chunk-PEAKCOUW.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-Y2OHHNM2.js.map +0 -1
- package/dist/domEditingLayers-PYST4G45.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/player.js.map +0 -1
- package/dist/styles/tailwind-preset.js.map +0 -1
- package/dist/ui.js.map +0 -1
package/dist/player.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Player
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-PHGBG7OG.js";
|
|
4
|
+
import "./chunk-PVFBY7KR.js";
|
|
5
|
+
import "./chunk-T43DM7Q6.js";
|
|
6
|
+
import "./chunk-MLKGABMK.js";
|
|
7
7
|
export {
|
|
8
8
|
Player
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=player.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../chunk-
|
|
1
|
+
import "../chunk-MLKGABMK.js";
|
|
2
2
|
|
|
3
3
|
// src/styles/tailwind-preset.shared.js
|
|
4
4
|
var studioPreset = {
|
|
@@ -47,4 +47,3 @@ var tailwind_preset_default = tailwind_preset_shared_default;
|
|
|
47
47
|
export {
|
|
48
48
|
tailwind_preset_default as default
|
|
49
49
|
};
|
|
50
|
-
//# sourceMappingURL=tailwind-preset.js.map
|
package/dist/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HyperframesLogo
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-C5BDT7NG.js";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
ContextMenu,
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
fieldBase,
|
|
31
31
|
fieldText,
|
|
32
32
|
popupSurface
|
|
33
|
-
} from "./chunk-
|
|
34
|
-
import "./chunk-
|
|
33
|
+
} from "./chunk-PVFBY7KR.js";
|
|
34
|
+
import "./chunk-MLKGABMK.js";
|
|
35
35
|
export {
|
|
36
36
|
Button,
|
|
37
37
|
ContextMenu,
|
|
@@ -63,4 +63,3 @@ export {
|
|
|
63
63
|
fieldText,
|
|
64
64
|
popupSurface
|
|
65
65
|
};
|
|
66
|
-
//# sourceMappingURL=ui.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.146",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"dockview-react": "8.3.1",
|
|
61
61
|
"gsap": "^3.13.0",
|
|
62
62
|
"mediabunny": "^1.45.3",
|
|
63
|
-
"@hyperframes/core": "0.8.
|
|
64
|
-
"@hyperframes/parsers": "0.8.
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/
|
|
67
|
-
"@hyperframes/
|
|
68
|
-
"@hyperframes/
|
|
63
|
+
"@hyperframes/core": "0.8.146",
|
|
64
|
+
"@hyperframes/parsers": "0.8.146",
|
|
65
|
+
"@hyperframes/player": "0.8.146",
|
|
66
|
+
"@hyperframes/sdk": "0.8.146",
|
|
67
|
+
"@hyperframes/studio-server": "0.8.146",
|
|
68
|
+
"@hyperframes/shader-transitions": "0.8.146"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"vite": "^6.4.2",
|
|
83
83
|
"vitest": "^4.1.11",
|
|
84
84
|
"zustand": "^5.0.0",
|
|
85
|
-
"@hyperframes/producer": "0.8.
|
|
85
|
+
"@hyperframes/producer": "0.8.146"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"react": "19",
|
|
@@ -408,7 +408,7 @@ export const CaptionOverlay = memo(function CaptionOverlay({ iframeRef }: Captio
|
|
|
408
408
|
{wordBoxes.length === 0 && model && model.segments.size > 0 && (
|
|
409
409
|
<div className="absolute inset-x-0 top-3 flex justify-center pointer-events-none">
|
|
410
410
|
<span className="px-2.5 py-1 rounded-full bg-black/60 border border-neutral-700 text-2xs text-neutral-300">
|
|
411
|
-
No captions visible at this frame
|
|
411
|
+
No captions visible at this frame. Scrub to a caption, or select one in the track below
|
|
412
412
|
</span>
|
|
413
413
|
</div>
|
|
414
414
|
)}
|
|
@@ -235,7 +235,7 @@ export function useCaptionSync(projectId: string | null) {
|
|
|
235
235
|
// File exists but is unreadable — previous edits would silently not load.
|
|
236
236
|
useCaptionStore
|
|
237
237
|
.getState()
|
|
238
|
-
.setSyncError("caption-overrides.json is corrupt
|
|
238
|
+
.setSyncError("caption-overrides.json is corrupt, so earlier caption edits didn't load");
|
|
239
239
|
}
|
|
240
240
|
}, []);
|
|
241
241
|
|
|
@@ -50,8 +50,8 @@ export function LinkedSelectionToggle() {
|
|
|
50
50
|
<Tooltip
|
|
51
51
|
label={
|
|
52
52
|
linkedSelection
|
|
53
|
-
? "Linked Selection on
|
|
54
|
-
: "Linked Selection off
|
|
53
|
+
? "Linked Selection on: a click selects both halves (⌥-click for one)"
|
|
54
|
+
: "Linked Selection off: a click selects one clip"
|
|
55
55
|
}
|
|
56
56
|
>
|
|
57
57
|
<button
|
|
@@ -113,11 +113,7 @@ export function LintModal({
|
|
|
113
113
|
: "bg-accent hover:bg-accent-hover text-on-accent"
|
|
114
114
|
}`}
|
|
115
115
|
>
|
|
116
|
-
{copied
|
|
117
|
-
? "Copied!"
|
|
118
|
-
: copyFailed
|
|
119
|
-
? "Copy failed — check permissions"
|
|
120
|
-
: "Copy to Agent"}
|
|
116
|
+
{copied ? "Copied!" : copyFailed ? "Copy failed. Check permissions" : "Copy to Agent"}
|
|
121
117
|
</button>
|
|
122
118
|
</div>
|
|
123
119
|
)}
|
|
@@ -23,7 +23,7 @@ function MediaErrorPanel({ name, filePath }: { name: string; filePath: string })
|
|
|
23
23
|
<span className="text-sm text-neutral-400 font-medium">{name}</span>
|
|
24
24
|
<span className="text-[11px] text-neutral-600 font-mono">{filePath}</span>
|
|
25
25
|
<span className="text-[10px] text-neutral-500">
|
|
26
|
-
Couldn't load this file
|
|
26
|
+
Couldn't load this file. It may be missing or corrupt
|
|
27
27
|
</span>
|
|
28
28
|
</div>
|
|
29
29
|
);
|
|
@@ -110,7 +110,7 @@ export function MediaPreview({ projectId, filePath }: { projectId: string; fileP
|
|
|
110
110
|
</svg>
|
|
111
111
|
<span className="text-sm text-neutral-400 font-medium">{name}</span>
|
|
112
112
|
<span className="text-[11px] text-neutral-600 font-mono">{filePath}</span>
|
|
113
|
-
<span className="text-[10px] text-neutral-600">Binary file
|
|
113
|
+
<span className="text-[10px] text-neutral-600">Binary file: preview not available</span>
|
|
114
114
|
</div>
|
|
115
115
|
);
|
|
116
116
|
}
|
|
@@ -82,7 +82,7 @@ export function ProjectUnreachableBanner({ projectId }: ProjectUnreachableBanner
|
|
|
82
82
|
</button>
|
|
83
83
|
</>
|
|
84
84
|
) : (
|
|
85
|
-
<span>Couldn't open this project
|
|
85
|
+
<span>Couldn't open this project. It may have been renamed, moved, or deleted.</span>
|
|
86
86
|
)}
|
|
87
87
|
</div>
|
|
88
88
|
);
|
|
@@ -75,8 +75,8 @@ export const StudioLeftPanels = memo(function StudioLeftPanels({
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
await waitForPendingDomEditSaves();
|
|
78
|
-
const { format, quality, fps } = getPersistedRenderSettings();
|
|
79
|
-
await renderQueue.startRender({ composition: comp, format, quality, fps });
|
|
78
|
+
const { format, quality, fps, gpu } = getPersistedRenderSettings();
|
|
79
|
+
await renderQueue.startRender({ composition: comp, format, quality, fps, gpu });
|
|
80
80
|
},
|
|
81
81
|
[renderQueue, waitForPendingDomEditSaves],
|
|
82
82
|
);
|
|
@@ -174,7 +174,7 @@ export const StudioRightPanels = memo(function StudioRightPanels({
|
|
|
174
174
|
// timeline offers that write as a mute. Checked here as well as in the panel
|
|
175
175
|
// because the button is not the only caller.
|
|
176
176
|
if (!canHideSelections(domEditGroupSelections)) {
|
|
177
|
-
showToast("Audio can't be hidden
|
|
177
|
+
showToast("Audio can't be hidden. Use the group's own controls", "info");
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
const { elements } = usePlayerStore.getState();
|
|
@@ -82,7 +82,7 @@ describe("TimelineToolbar — adaptive thumbnails", () => {
|
|
|
82
82
|
it("keeps a user-controlled hidden mode as the rollback path", () => {
|
|
83
83
|
const { host, root } = renderToolbar();
|
|
84
84
|
const button = host.querySelector<HTMLButtonElement>(
|
|
85
|
-
'button[aria-label="Hide thumbnails
|
|
85
|
+
'button[aria-label="Hide thumbnails: labels only"]',
|
|
86
86
|
);
|
|
87
87
|
if (!button) throw new Error("thumbnail toggle not rendered");
|
|
88
88
|
|
|
@@ -90,7 +90,7 @@ describe("TimelineToolbar — adaptive thumbnails", () => {
|
|
|
90
90
|
|
|
91
91
|
expect(usePlayerStore.getState().thumbnailMode).toBe("hidden");
|
|
92
92
|
expect(button.getAttribute("aria-label")).toBe(
|
|
93
|
-
"Show thumbnails
|
|
93
|
+
"Show thumbnails: posters stay visible; richer previews appear on interaction",
|
|
94
94
|
);
|
|
95
95
|
act(() => root.unmount());
|
|
96
96
|
});
|
|
@@ -235,8 +235,8 @@ export const TimelineToolbar = memo(function TimelineToolbar({
|
|
|
235
235
|
<Tooltip
|
|
236
236
|
label={
|
|
237
237
|
rippleEditEnabled
|
|
238
|
-
? "Ripple on
|
|
239
|
-
: "Ripple off
|
|
238
|
+
? "Ripple on: keeps the main track gapless"
|
|
239
|
+
: "Ripple off: deleting a main-track clip leaves a gap"
|
|
240
240
|
}
|
|
241
241
|
>
|
|
242
242
|
<button
|
|
@@ -465,16 +465,16 @@ export const TimelineToolbar = memo(function TimelineToolbar({
|
|
|
465
465
|
<Tooltip
|
|
466
466
|
label={
|
|
467
467
|
thumbnailsVisible
|
|
468
|
-
? "Hide thumbnails
|
|
469
|
-
: "Show thumbnails
|
|
468
|
+
? "Hide thumbnails: labels only"
|
|
469
|
+
: "Show thumbnails: posters stay visible; richer previews appear on interaction"
|
|
470
470
|
}
|
|
471
471
|
>
|
|
472
472
|
<button
|
|
473
473
|
type="button"
|
|
474
474
|
aria-label={
|
|
475
475
|
thumbnailsVisible
|
|
476
|
-
? "Hide thumbnails
|
|
477
|
-
: "Show thumbnails
|
|
476
|
+
? "Hide thumbnails: labels only"
|
|
477
|
+
: "Show thumbnails: posters stay visible; richer previews appear on interaction"
|
|
478
478
|
}
|
|
479
479
|
aria-pressed={thumbnailsVisible}
|
|
480
480
|
onClick={() => setThumbnailMode(thumbnailsVisible ? "hidden" : "adaptive")}
|
|
@@ -217,7 +217,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
217
217
|
{typeof animation.position === "number"
|
|
218
218
|
? `${parseFloat(animation.position.toFixed(3))}s`
|
|
219
219
|
: animation.position}{" "}
|
|
220
|
-
|
|
220
|
+
to {typeof endTime === "number" ? `${parseFloat(endTime.toFixed(3))}s` : endTime}
|
|
221
221
|
</span>
|
|
222
222
|
<span
|
|
223
223
|
className={`ml-auto text-[10px] ${flat ? "text-panel-text-3" : "text-neutral-500"}`}
|
|
@@ -256,7 +256,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
256
256
|
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
|
|
257
257
|
}}
|
|
258
258
|
/>
|
|
259
|
-
Keyframed
|
|
259
|
+
Keyframed: click a segment below to edit its curve
|
|
260
260
|
</p>
|
|
261
261
|
)}
|
|
262
262
|
{runEase && runEase !== "none" && (
|
|
@@ -273,7 +273,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
273
273
|
setTimeout(() => setCopied(false), 1500);
|
|
274
274
|
}}
|
|
275
275
|
className="shrink-0 rounded-sm px-1.5 py-0.5 text-[9px] font-medium text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
|
|
276
|
-
title="Copy description
|
|
276
|
+
title="Copy description for agent prompts"
|
|
277
277
|
>
|
|
278
278
|
{copied ? "Copied" : "Copy"}
|
|
279
279
|
</button>
|
|
@@ -93,7 +93,7 @@ export function PropertyRow({
|
|
|
93
93
|
onClick={() => onCommit(isVisible ? "hidden" : "visible")}
|
|
94
94
|
className="shrink-0 rounded-full transition-colors duration-200 relative"
|
|
95
95
|
style={{ width: 28, height: 16, background: isVisible ? P.accent : P.borderInput }}
|
|
96
|
-
title={isVisible ? "Visible
|
|
96
|
+
title={isVisible ? "Visible: click to hide" : "Hidden: click to show"}
|
|
97
97
|
>
|
|
98
98
|
<span
|
|
99
99
|
className="absolute top-[2px] left-0 rounded-full transition-transform duration-200"
|
|
@@ -58,7 +58,7 @@ export const BlockParamsPanel = memo(function BlockParamsPanel({
|
|
|
58
58
|
if (matches === 0) {
|
|
59
59
|
setCommitState({
|
|
60
60
|
tone: "error",
|
|
61
|
-
message: `Couldn't find the current value in ${compositionPath}
|
|
61
|
+
message: `Couldn't find the current value in ${compositionPath}. It may have been edited by hand.`,
|
|
62
62
|
});
|
|
63
63
|
trackBlockParamCommit({ tone: "error", blockName, key });
|
|
64
64
|
return;
|
|
@@ -73,7 +73,7 @@ export const BlockParamsPanel = memo(function BlockParamsPanel({
|
|
|
73
73
|
if (matches > 1) {
|
|
74
74
|
setCommitState({
|
|
75
75
|
tone: "error",
|
|
76
|
-
message: `"${previous}" appears ${matches}× in ${compositionPath}
|
|
76
|
+
message: `"${previous}" appears ${matches}× in ${compositionPath}. The panel can't tell which one belongs to this parameter, so it won't risk changing unrelated content. Edit the file directly to disambiguate.`,
|
|
77
77
|
});
|
|
78
78
|
trackBlockParamCommit({ tone: "error", blockName, key });
|
|
79
79
|
return;
|
|
@@ -171,7 +171,7 @@ export const BlockParamsPanel = memo(function BlockParamsPanel({
|
|
|
171
171
|
)}
|
|
172
172
|
{!fileManager && params.length > 0 && (
|
|
173
173
|
<div className="text-[10px] text-warning-ink">
|
|
174
|
-
Block params can't be edited here
|
|
174
|
+
Block params can't be edited here: no project file access.
|
|
175
175
|
</div>
|
|
176
176
|
)}
|
|
177
177
|
{params.map((param) => (
|
|
@@ -17,14 +17,14 @@ export function ComputedTweenNotice({
|
|
|
17
17
|
if (editability === "source") {
|
|
18
18
|
return (
|
|
19
19
|
<div className="rounded-md border border-neutral-800 bg-neutral-900/50 px-2 py-1.5 text-[9px] text-neutral-400">
|
|
20
|
-
Computed value
|
|
20
|
+
Computed value. Edit it in the Code tab.
|
|
21
21
|
</div>
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
const source = provenance?.fn ? `${provenance.fn}()` : "a loop";
|
|
25
25
|
return (
|
|
26
26
|
<div className="flex items-center justify-between gap-2 rounded-md border border-neutral-800 bg-neutral-900/50 px-2 py-1.5 text-[9px] text-neutral-400">
|
|
27
|
-
<span>Generated by {source}
|
|
27
|
+
<span>Generated by {source}, not directly editable.</span>
|
|
28
28
|
{onUnroll && (
|
|
29
29
|
<button
|
|
30
30
|
type="button"
|
|
@@ -293,7 +293,7 @@ export const FileTree = memo(function FileTree({
|
|
|
293
293
|
)}
|
|
294
294
|
{children.length === 0 && !inlineInput && (
|
|
295
295
|
<div className="px-3 py-4 text-center text-[11px] text-neutral-600">
|
|
296
|
-
No files yet{hasFileOps ? "
|
|
296
|
+
No files yet{hasFileOps ? ". Use + above to create one" : ""}.
|
|
297
297
|
</div>
|
|
298
298
|
)}
|
|
299
299
|
{children.map((child) =>
|
|
@@ -63,7 +63,7 @@ export function GsapAnimationList({
|
|
|
63
63
|
<div className="space-y-2">
|
|
64
64
|
{animations.length === 0 && (
|
|
65
65
|
<p className="text-[11px] leading-4 text-neutral-500">
|
|
66
|
-
No animations on this element yet
|
|
66
|
+
No animations on this element yet. Add an effect below to animate it.
|
|
67
67
|
</p>
|
|
68
68
|
)}
|
|
69
69
|
{animations.map((animation, index) => (
|
|
@@ -25,8 +25,8 @@ export const GsapAnimationSection = memo(function GsapAnimationSection({
|
|
|
25
25
|
<Section title="Animation" icon={<Film size={15} />}>
|
|
26
26
|
{multipleTimelines && (
|
|
27
27
|
<p className="mb-2 rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink">
|
|
28
|
-
This file has multiple GSAP timelines. Animation editing is disabled to prevent data loss
|
|
29
|
-
|
|
28
|
+
This file has multiple GSAP timelines. Animation editing is disabled to prevent data loss.
|
|
29
|
+
Consolidate into a single timeline to enable editing.
|
|
30
30
|
</p>
|
|
31
31
|
)}
|
|
32
32
|
{unsupportedTimelinePattern && (
|
|
@@ -9,7 +9,7 @@ import type { DomEditLayerItem } from "./domEditingTypes";
|
|
|
9
9
|
import { createRafThrottle, LayersPanel, sortLayersByZIndex } from "./LayersPanel";
|
|
10
10
|
import { isLayerDraggable } from "./useLayerDrag";
|
|
11
11
|
import { liveTime } from "../../player";
|
|
12
|
-
import { sceneSwapFor } from "../../player/sceneSwap";
|
|
12
|
+
import { announcePreviewDocumentLoaded, sceneSwapFor } from "../../player/sceneSwap";
|
|
13
13
|
|
|
14
14
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
15
15
|
|
|
@@ -284,12 +284,12 @@ describe("LayersPanel preview promotion", () => {
|
|
|
284
284
|
mocks.collect.mockClear();
|
|
285
285
|
|
|
286
286
|
act(() => {
|
|
287
|
-
a
|
|
287
|
+
announcePreviewDocumentLoaded(a);
|
|
288
288
|
});
|
|
289
289
|
expect(mocks.collect).not.toHaveBeenCalled();
|
|
290
290
|
|
|
291
291
|
act(() => {
|
|
292
|
-
b
|
|
292
|
+
announcePreviewDocumentLoaded(b);
|
|
293
293
|
});
|
|
294
294
|
expect(collectedRootIds()).toEqual(["b"]);
|
|
295
295
|
});
|
|
@@ -325,7 +325,7 @@ describe("LayersPanel preview promotion", () => {
|
|
|
325
325
|
mocks.previewIframeRef.current = iframe;
|
|
326
326
|
usePreviewIframeStore.setState({ iframe });
|
|
327
327
|
act(() => root.render(createElement(LayersPanel)));
|
|
328
|
-
act(() => void iframe
|
|
328
|
+
act(() => void announcePreviewDocumentLoaded(iframe));
|
|
329
329
|
|
|
330
330
|
const rows = [...host.querySelectorAll("[data-layer-index]")];
|
|
331
331
|
expect(rows).toHaveLength(2);
|
|
@@ -89,8 +89,8 @@ interface CollapsedState {
|
|
|
89
89
|
[key: string]: boolean;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/** Studio state the panel reads; passed by hosts outside Studio's providers. Keep previewIframeRef stable
|
|
93
|
-
*
|
|
92
|
+
/** Studio state the panel reads; passed by hosts outside Studio's providers. Keep previewIframeRef stable;
|
|
93
|
+
* a host with its own player calls announcePreviewDocumentLoaded on each new page, or bumps refreshKey. */
|
|
94
94
|
export type LayersPanelHost = Pick<
|
|
95
95
|
StudioShellValue,
|
|
96
96
|
"previewIframeRef" | "activeCompPath" | "showToast"
|
|
@@ -127,7 +127,7 @@ export function OffCanvasIndicators({
|
|
|
127
127
|
aria-label={`Select off-canvas element ${r.key}`}
|
|
128
128
|
className="pointer-events-auto absolute inset-0 border-2 border-dashed border-studio-accent/10 rounded-md cursor-pointer hover:border-studio-accent hover:bg-studio-accent/10 transition-colors"
|
|
129
129
|
style={clipOutside ? { clipPath: clipOutside } : undefined}
|
|
130
|
-
title={`Off-canvas: ${r.key}
|
|
130
|
+
title={`Off-canvas: ${r.key}. Click to select`}
|
|
131
131
|
onClick={handleClick}
|
|
132
132
|
onKeyDown={(e) => {
|
|
133
133
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -50,7 +50,7 @@ export function PromotableControl({
|
|
|
50
50
|
useEffect(() => {
|
|
51
51
|
if (danglingId != null) {
|
|
52
52
|
console.warn(
|
|
53
|
-
`[hyperframes] Control is bound to variable "${danglingId}", but no such declaration exists. The element still carries the binding on disk
|
|
53
|
+
`[hyperframes] Control is bound to variable "${danglingId}", but no such declaration exists. The element still carries the binding on disk. Re-declare the variable or unbind the element.`,
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
}, [danglingId]);
|
|
@@ -240,13 +240,13 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
|
|
240
240
|
navigator.clipboard
|
|
241
241
|
.writeText(text)
|
|
242
242
|
.then(() => {
|
|
243
|
-
showToast(`Copied element info for ${element.label}
|
|
243
|
+
showToast(`Copied element info for ${element.label}. Paste it into any AI agent`, "info");
|
|
244
244
|
setClipboardCopied(true);
|
|
245
245
|
clearTimeout(clipboardTimerRef.current);
|
|
246
246
|
clipboardTimerRef.current = setTimeout(() => setClipboardCopied(false), 1500);
|
|
247
247
|
})
|
|
248
248
|
.catch(() => {
|
|
249
|
-
showToast("Couldn't copy to the clipboard
|
|
249
|
+
showToast("Couldn't copy to the clipboard. Check browser permissions", "error");
|
|
250
250
|
});
|
|
251
251
|
};
|
|
252
252
|
|
|
@@ -160,8 +160,8 @@ export const SnapToolbar = memo(function SnapToolbar() {
|
|
|
160
160
|
}}
|
|
161
161
|
title={
|
|
162
162
|
prefs.gridVisible
|
|
163
|
-
? "Grid visible (G)
|
|
164
|
-
: "Grid hidden (G)
|
|
163
|
+
? "Grid visible (G). Right-click for spacing options"
|
|
164
|
+
: "Grid hidden (G). Right-click for spacing options"
|
|
165
165
|
}
|
|
166
166
|
aria-label="Toggle grid"
|
|
167
167
|
>
|
|
@@ -5,6 +5,7 @@ import { createRoot, type Root } from "react-dom/client";
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { studioEditLifecycle, type StudioWriteResult } from "../../webmcp/writeCoordinator";
|
|
7
7
|
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
8
|
+
import { announcePreviewDocumentLoaded } from "../../player/sceneSwap";
|
|
8
9
|
import { TopologyLens } from "./TopologyLens";
|
|
9
10
|
|
|
10
11
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
@@ -259,7 +260,7 @@ describe("TopologyLens", () => {
|
|
|
259
260
|
mount();
|
|
260
261
|
begin();
|
|
261
262
|
|
|
262
|
-
act(() => iframe
|
|
263
|
+
act(() => announcePreviewDocumentLoaded(iframe!));
|
|
263
264
|
expect(host?.querySelector('[data-topology-lens="hidden"]')).not.toBeNull();
|
|
264
265
|
|
|
265
266
|
begin();
|
|
@@ -286,7 +287,7 @@ describe("TopologyLens", () => {
|
|
|
286
287
|
root = null;
|
|
287
288
|
|
|
288
289
|
expect(vi.getTimerCount()).toBe(1);
|
|
289
|
-
expect(removeListener).toHaveBeenCalledWith("
|
|
290
|
+
expect(removeListener).toHaveBeenCalledWith("hf-preview-document-loaded", expect.any(Function));
|
|
290
291
|
expect(studioEditLifecycle.getSnapshot()).toMatchObject({ phase: "dispatching" });
|
|
291
292
|
act(() => vi.advanceTimersByTime(0));
|
|
292
293
|
expect(vi.getTimerCount()).toBe(0);
|
|
@@ -312,10 +313,10 @@ describe("TopologyLens", () => {
|
|
|
312
313
|
ref.current = b;
|
|
313
314
|
usePreviewIframeStore.getState().setIframe(b);
|
|
314
315
|
});
|
|
315
|
-
act(() => a
|
|
316
|
+
act(() => announcePreviewDocumentLoaded(a));
|
|
316
317
|
expect(host.querySelector('[data-topology-lens="hidden"]')).toBeNull();
|
|
317
318
|
|
|
318
|
-
act(() => b
|
|
319
|
+
act(() => announcePreviewDocumentLoaded(b));
|
|
319
320
|
expect(host.querySelector('[data-topology-lens="hidden"]')).not.toBeNull();
|
|
320
321
|
usePreviewIframeStore.setState({ iframe: null });
|
|
321
322
|
});
|
|
@@ -308,7 +308,7 @@ export function Transform3DCube({
|
|
|
308
308
|
onClick={onKeyframe}
|
|
309
309
|
title={
|
|
310
310
|
keyframed
|
|
311
|
-
? "3D transform is keyframed
|
|
311
|
+
? "3D transform is keyframed. Click a field diamond to add keyframes"
|
|
312
312
|
: "Keyframe the 3D transform (animate it over time)"
|
|
313
313
|
}
|
|
314
314
|
aria-label="Keyframe 3D transform"
|
|
@@ -8,7 +8,7 @@ export const METHOD_LABELS: Record<string, string> = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export const METHOD_TOOLTIPS: Record<string, string> = {
|
|
11
|
-
set: "Instantly snap to these values
|
|
11
|
+
set: "Instantly snap to these values, no transition",
|
|
12
12
|
to: "Smoothly animate the element to these target values",
|
|
13
13
|
from: "Element starts at these values and transitions to its normal state",
|
|
14
14
|
fromTo: "Animate from one state to another",
|
|
@@ -83,7 +83,7 @@ export const PROP_TOOLTIPS: Record<string, string> = {
|
|
|
83
83
|
perspective:
|
|
84
84
|
"3D depth context for child elements; set it on a parent when rotating children in 3D",
|
|
85
85
|
transformPerspective:
|
|
86
|
-
"3D depth for THIS element's own X/Y rotation
|
|
86
|
+
"3D depth for THIS element's own X/Y rotation: lower = stronger perspective (try 600 to 1000)",
|
|
87
87
|
transformOrigin: "Pivot point for transforms, for example center center or 50% 50%",
|
|
88
88
|
width: "Element width",
|
|
89
89
|
height: "Element height",
|
|
@@ -109,7 +109,7 @@ describe("buildTweenSummary", () => {
|
|
|
109
109
|
const s = buildTweenSummary(
|
|
110
110
|
anim({ method: "fromTo", fromProperties: {}, properties: { scale: 2 } }),
|
|
111
111
|
);
|
|
112
|
-
expect(s).toContain("from [
|
|
112
|
+
expect(s).toContain("from [none]");
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
it("handles no properties", () => {
|
|
@@ -64,7 +64,7 @@ export function buildTweenSummary(animation: GsapAnimation): string {
|
|
|
64
64
|
const label = (PROP_LABELS[p] ?? p).toLowerCase();
|
|
65
65
|
return `${label} ${formatPropValue(p, v)}`;
|
|
66
66
|
});
|
|
67
|
-
const fromText = fromDescs.length > 0 ? fromDescs.join(", ") : "
|
|
67
|
+
const fromText = fromDescs.length > 0 ? fromDescs.join(", ") : "none";
|
|
68
68
|
return `Starting at ${pos}s, over ${dur}s, ${target} animates from [${fromText}] to [${propText}] using a ${ease.toLowerCase()} curve.`;
|
|
69
69
|
}
|
|
70
70
|
return `Starting at ${pos}s, over ${dur}s, animate ${target}'s ${propText} using a ${ease.toLowerCase()} curve.`;
|
|
@@ -423,7 +423,7 @@ export function ColorGradingControls({
|
|
|
423
423
|
void actions
|
|
424
424
|
.importLut(files, onImportAssets, () => track("button", "Import LUT"))
|
|
425
425
|
.catch(() =>
|
|
426
|
-
setLutImportError("LUT import failed
|
|
426
|
+
setLutImportError("LUT import failed. Check the .cube file and try again."),
|
|
427
427
|
)
|
|
428
428
|
.finally(() => setLutImporting(false));
|
|
429
429
|
event.currentTarget.value = "";
|
|
@@ -135,7 +135,7 @@ export function ImageFillField({
|
|
|
135
135
|
commitImage(toProjectRootAssetPath(nextImage), true);
|
|
136
136
|
}
|
|
137
137
|
} catch {
|
|
138
|
-
setUploadError("Upload failed
|
|
138
|
+
setUploadError("Upload failed. Check the file and try again.");
|
|
139
139
|
} finally {
|
|
140
140
|
setUploading(false);
|
|
141
141
|
}
|
|
@@ -100,7 +100,7 @@ export function FlatTimingRow({
|
|
|
100
100
|
{cell("Duration", formatTimingValue(duration), commitDuration)}
|
|
101
101
|
{derived && (
|
|
102
102
|
<p className="col-span-3 mt-1 text-[10px] leading-snug text-panel-text-3">
|
|
103
|
-
Inferred from this element's animation
|
|
103
|
+
Inferred from this element's animation. Edit to pin an explicit clip range.
|
|
104
104
|
</p>
|
|
105
105
|
)}
|
|
106
106
|
</div>
|
|
@@ -150,7 +150,7 @@ export function FlatMotionSection({
|
|
|
150
150
|
{multipleTimelines && (
|
|
151
151
|
<p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink">
|
|
152
152
|
This file has multiple GSAP timelines. Animation editing is disabled to prevent data
|
|
153
|
-
loss
|
|
153
|
+
loss. Consolidate into a single timeline to enable editing.
|
|
154
154
|
</p>
|
|
155
155
|
)}
|
|
156
156
|
{unsupportedTimelinePattern && (
|
|
@@ -195,7 +195,7 @@ export function motionSectionLabel(args: {
|
|
|
195
195
|
if (args.timingOnly) {
|
|
196
196
|
return {
|
|
197
197
|
title: "Timing",
|
|
198
|
-
summary: `${formatTimingValue(args.start)}
|
|
198
|
+
summary: `${formatTimingValue(args.start)} to ${formatTimingValue(args.start + args.duration)}`,
|
|
199
199
|
};
|
|
200
200
|
}
|
|
201
201
|
return {
|
|
@@ -72,7 +72,7 @@ export function FlatRow({
|
|
|
72
72
|
<button
|
|
73
73
|
type="button"
|
|
74
74
|
data-flat-row-reset="true"
|
|
75
|
-
title="Remove
|
|
75
|
+
title="Remove and fall back to default"
|
|
76
76
|
onClick={() => {
|
|
77
77
|
track("button", `Reset ${label}`);
|
|
78
78
|
onReset();
|
|
@@ -556,7 +556,7 @@ export function FlatSlider({
|
|
|
556
556
|
<button
|
|
557
557
|
type="button"
|
|
558
558
|
data-flat-slider-reset="true"
|
|
559
|
-
title="Remove
|
|
559
|
+
title="Remove and fall back to default"
|
|
560
560
|
disabled={disabled}
|
|
561
561
|
onClick={() => {
|
|
562
562
|
track("button", `Reset ${label}`);
|
|
@@ -90,7 +90,7 @@ export function FlatSelectRow({
|
|
|
90
90
|
<button
|
|
91
91
|
type="button"
|
|
92
92
|
data-flat-select-reset="true"
|
|
93
|
-
title="Remove
|
|
93
|
+
title="Remove and fall back to default"
|
|
94
94
|
disabled={disabled}
|
|
95
95
|
onClick={() => {
|
|
96
96
|
track("button", `Reset ${trackName}`);
|
|
@@ -35,7 +35,7 @@ const ALIGN_OPTIONS = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
const CASE_OPTIONS = [
|
|
38
|
-
{ key: "none", label: "none", node: "
|
|
38
|
+
{ key: "none", label: "none", node: "None" },
|
|
39
39
|
{ key: "uppercase", label: "uppercase", node: "AG" },
|
|
40
40
|
{ key: "lowercase", label: "lowercase", node: "ag" },
|
|
41
41
|
{ key: "capitalize", label: "capitalize", node: "Ag" },
|